use super::tobmuxregister::TOBMuxRegister;

#[derive(Clone)]
pub struct TOBMux {
    mux_size: usize,
    regs: Vec<TOBMuxRegister>,    
}

impl TOBMux {
    pub fn with_size(mux_size: usize) -> Self {
        Self {
            mux_size,
            regs: (0..mux_size).into_iter()
                .map(|_| TOBMuxRegister::empty() )
                .collect()
        }
    }
}

impl TOBMux {
    pub fn index_map(&self, input_idx: usize) -> Option<usize> {
        assert!(input_idx < self.mux_size); 
        self.regs[input_idx].get()
    }
}

impl TOBMux {
    pub fn register(&self, input_idx: usize) -> TOBMuxRegister {
        self.regs[input_idx].rc_clone()
    }
}

impl TOBMux {
    pub fn available_connectors(&self, input_idx: usize) -> Vec<TOBMuxConnector> {
        assert!(input_idx < self.mux_size, "input_idx '{}' < mux_size '{}'", input_idx, self.mux_size);

        // input idx already connected!
        if let Some(_) = self.regs[input_idx].get() {
            return vec![];
        }

        self.available_output_indexes().into_iter()
            .map(|outpu_idx| {
                TOBMuxConnector::new(
                    self.regs[input_idx].rc_clone(),
                    input_idx,
                    outpu_idx
                )
            })
            .collect()
    } 

    fn available_output_indexes(&self) -> Vec<usize> {
        // Search for each regs value
        let mut useds = vec![false; self.mux_size];
        for reg in &self.regs {
            if let Some(output_idx) = reg.get() {
                useds[output_idx] = true;
            }
        }

        useds.into_iter()
            .enumerate()
            .filter(|(_, used)| !used)
            .map(|(output_idx, _)| {
                output_idx
            })
            .collect()
    }
}

pub struct TOBMuxConnector {
    reg: TOBMuxRegister, 
    pub input_idx: usize,
    pub output_idx: usize,
}

impl TOBMuxConnector {
    pub fn new(reg: TOBMuxRegister, input_idx: usize, output_idx: usize) -> Self {
        Self { reg, input_idx, output_idx }
    }

    pub fn connect(&self) {
        self.reg.set(self.output_idx)
    }

    pub fn disconnect(&self) {
        self.reg.reset();
    }
}

impl TOBMuxConnector {
    pub fn rc_clone(&self) -> Self {
        Self {
            reg: self.reg.rc_clone(),
            input_idx: self.input_idx,
            output_idx: self.output_idx
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    
    #[test]
    fn test_available_output_indexes() {
        let tob = TOBMux::with_size(8);
        assert_eq!(
            tob.available_output_indexes(),
            vec![0, 1, 2, 3, 4, 5, 6, 7]
        );
    
        tob.regs[0].set(7);
        assert_eq!(
            tob.available_output_indexes(),
            vec![0, 1, 2, 3, 4, 5, 6]
        );

        tob.regs[0].set(3);
        assert_eq!(
            tob.available_output_indexes(),
            vec![0, 1, 2, 4, 5, 6, 7]
        );
    }

}